/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022.
 * Description: The arch timer proc interface is added and
 * the function of printing arch timer jump information is supported.
 * Author: liuwenliang <liuwenliang@huawei.com>
 * Create: 2022-08-15
 */
#include <linux/types.h>
#include <linux/sched_clock.h>
#include <linux/sched/isolation.h>
#include <linux/fs.h>
#include <linux/time64.h>
#include <linux/hal/acpi_op.h>
#include <linux/rtos_acpi_id.h>
#include <linux/sysctl.h>
#include <linux/kmemleak.h>

#include <asm/arch_timer.h>
#include <internal/arch_timer_jump_dfx.h>

#define TIMER_GAP_MAX_SECONDS 3600
#define TIMER_GAP_MIN_SECONDS 10
#define CNTCV_SIZE 8
#define PHYADDR_48BITS_MASK 0xffff000000000000ULL
#define ERR_CHECK_PERIOD_IN_SECOND 2
#define STRING_MAX_LENGTH 40
#define APPROXIMATE_DIFF_IN_CYCLE 100000

struct arch_timer_jump_info {
	atomic_t is_error_recorded;
	u64 old_cyc;
	u64 err_cyc;
	u64 syscnt_value;
};

struct arch_timer_jump_dfx {
	u64 arch_timer_max_interval;
	u64 last_counter_value;
	struct arch_timer_jump_info arch_timer_jump_info;
};

DEFINE_PER_CPU(struct arch_timer_jump_dfx, arch_timer_jump_dfx);
DEFINE_STATIC_KEY_FALSE(arch_timer_jump_dfx_enable);

static DEFINE_MUTEX(arch_timer_jump_dfx_mutex);
static void __iomem *syscnt_vaddr;
static struct hrtimer hrtimer_timer_exception;
static u64 arch_timer_threshold_in_cyc;

static u64 ns_to_cyc(u64 ns)
{
	u64 cyc;
	u32 seq;
	struct clock_read_data *rd;

	do {
		rd = sched_clock_read_begin(&seq);
		cyc = (ns << rd->shift) / rd->mult;
	} while (sched_clock_read_retry(seq));
	return cyc;
}

static u64 cyc_to_ns(u64 cyc)
{
	u64 ns;
	u32 seq;
	struct clock_read_data *rd;
	do {
		rd = sched_clock_read_begin(&seq);
		ns = (cyc * rd->mult) >> rd->shift;
	} while (sched_clock_read_retry(seq));
	return ns;
}

static ssize_t proc_arch_timer_counter_read(struct file *filp, char __user *buf, size_t len, loff_t *loff)
{
	unsigned int cpu;
	unsigned int cpu_id;
	int ret;
	u64 max_gap = 0;
	u64 gap;
	u64 ns;
	struct arch_timer_jump_dfx *jump_dfx;
	char buffer[STRING_MAX_LENGTH];

	(void)filp;
	for_each_present_cpu(cpu) {
		jump_dfx = per_cpu_ptr(&arch_timer_jump_dfx, cpu);
		gap = jump_dfx->arch_timer_max_interval;
		if (gap > max_gap) {
			max_gap = gap;
			cpu_id = cpu;
		}
	}

	ns = cyc_to_ns(max_gap);
	ret = snprintf(buffer, STRING_MAX_LENGTH, "max gap: %llu ns, cpu id: %u\n", ns, cpu_id);
	if (ret < 0) {
		pr_err("arch timer read max gap fail\n");
		return -EINVAL;
	}

	return simple_read_from_buffer(buf, len, loff, buffer, strlen(buffer));
}

static const struct proc_ops arch_timer_counter_fops = {
	.proc_read = proc_arch_timer_counter_read,
};

static void set_arch_timer_jump_dfx(int state)
{
	if (state == 1)
		static_branch_enable(&arch_timer_jump_dfx_enable);
	else
		static_branch_disable(&arch_timer_jump_dfx_enable);
}

static int sysctl_update_arch_timer_jump_dfx(struct ctl_table *table, int write,
			   void __user *buffer, size_t *lenp, loff_t *ppos)
{
	struct ctl_table t;
	int err;
	int state;

	if (static_branch_likely(&arch_timer_jump_dfx_enable))
		state = 1;
	else
		state = 0;

	if (write && !capable(CAP_SYS_ADMIN))
		return -EPERM;

	t = *table;
	t.data = &state;
	err = proc_dointvec_minmax(&t, write, buffer, lenp, ppos);
	if (err < 0)
		return err;

	if (write)
		set_arch_timer_jump_dfx(state);

	return err;
}

static struct ctl_table arch_timer_jump_dfx_table[] = {
	{
		.procname     = "arch_timer_jump_dfx",
		.data         = NULL,
		.maxlen       = sizeof(unsigned int),
		.mode         = 0600,
		.proc_handler = sysctl_update_arch_timer_jump_dfx,
		.extra1       = SYSCTL_ZERO,
		.extra2       = SYSCTL_ONE,
	},
	{}
};

/*
 * To get the value from the syscnt register proceed as follows:
 * 1. Read the upper 32-bit syscnt register
 * 2. Read the lower 32-bit syscnt register
 * 3. Read the upper 32-bit syscnt register again. If the value is
 *  different to the 32-bit upper value read previously, go back to step 2.
 *  Otherwise the 64-bit syscnt value is correct.
 */
static u64 notrace read_syscnt(void)
{
	u64 counter;
	u32 lower;
	u32 upper, old_upper;

	upper = readl_relaxed(syscnt_vaddr + 4);
	do {
		old_upper = upper;
		lower = readl_relaxed(syscnt_vaddr);
		upper = readl_relaxed(syscnt_vaddr + 4);
	} while (upper != old_upper);

	counter = upper;
	counter <<= 32;
	counter |= lower;
	return counter;
}

static void timer_jump_save_info(struct arch_timer_jump_dfx *jump_dfx,
	u64 err_cyc, u64 syscnt_value)
{
	struct arch_timer_jump_info *jump_info = &jump_dfx->arch_timer_jump_info;

	if (atomic_read(&jump_info->is_error_recorded) == 1)
		return;

	jump_info->old_cyc = jump_dfx->last_counter_value;
	jump_info->err_cyc = err_cyc;
	jump_info->syscnt_value = syscnt_value;

	smp_mb();
	atomic_set(&jump_info->is_error_recorded, 1);
}

static inline bool is_arch_timer_jump_dfx_enable(void)
{
	if (static_branch_unlikely(&arch_timer_jump_dfx_enable))
		return true;
	return false;
}

/*
 *  HI1620, as the following picture shows, the CNTPCT value is from syscnt reg.
 *  in this case, we can enable arch timer jump dfx.
 *  when get an abnormal cntpct value, read syscnt to try to self-healing.
 *  datas are defined as unsigned values, at the 100 m frequency, we do not consider the case of
 *  data overflow, which occurred more than 2000 years later.
 * +------------------------------------------------------------------------------------------------------------------------+
 * |     DIE                                                                                                                |
 * |                                     +-------------------------------------+                                            |
 * |                                     |           syscnt reg                |                                            |
 * |                                     +------------------|------------------+                                            |
 * |                                                        |                                                               |
 * |                          ------------------------------|---------------------------|--                                 |
 * |                          |                                                         |                                   |
 * |     +------------------------------------------------+    +------------------------|-----------------------+           |
 * |     | CPU0               |                           |    |  CPUX                  |                       |           |
 * |     |         +----------------------+               |    |           +----------------------+             |           |
 * |     |         |                      |               |    |           |                      |             |           |
 * |     |         |                      v               |    |           |                      v             |           |
 * |     |         |                +-----------+         |    |           |                +-----------+       |           |
 * |     |         |                |-CNTOFFSET |         |    |           |                |-CNTOFFSET |       |           |
 * |     |         |                +-----|-----+         |    |           |                +-----|-----+       |           |
 * |     |         |                      |               |    |           |                      |             |           |
 * |     |         |                      |               |    |           |                      |             |           |
 * |     |+--------v-------+   +----------v----------+    |    |  +--------v-------+   +----------v----------+  |           |
 * |     ||                |   |                     |    |    |  |                |   |                     |  |           |
 * |     || CNTPCT         |   | CNTVCT              |    |    |  | CNTPCT         |   | CNTVCT              |  |           |
 * |     ||                |   |(we do not care here)|    |    |  |                |   |(we do not care here)|  |           |
 * |     || physical timer |   | virtual timer       |    |    |  | physical timer |   | virtual timer       |  |           |
 * |     |+----------------+   +---------------------+    |    |  +----------------+   +---------------------+  |           |
 * |     |                                                |    |                                                |           |
 * |     +------------------------------------------------+    +------------------------------------------------+           |
 * |                                                                                                                        |
 * +------------------------------------------------------------------------------------------------------------------------+
 */
notrace u64 arch_counter_get_cntpct(void)
{
	unsigned long flags;
	u64 cur_cyc;
	u64 gap;
	u64 syscnt_value;
	struct arch_timer_jump_dfx *jump_dfx;

	if (!is_arch_timer_jump_dfx_enable() ||
			(!housekeeping_cpu(smp_processor_id(), HK_FLAG_TICK)))
		return __arch_counter_get_cntpct();

	local_irq_save(flags);

	jump_dfx = this_cpu_ptr(&arch_timer_jump_dfx);
	cur_cyc = __arch_counter_get_cntpct();
	gap = cur_cyc - jump_dfx->last_counter_value;
	/*
	 * to improve performance, gap is defined as an unsigned value, but not a signed value.
	 * we only need care whether gap is smaller than arch_timer_threshold_in_cyc, and do not need
	 * care whether gap is negative value.
	 * it can reduce one comparison.
	 *
	 * 1.cur_cyc < last_counter_value :
	 *   timer jump backward, gap is defined as an unsigned value, a negative number will be transformed
	 *   a big number which is bigger than arch_timer_threshold_in_cyc.
	 *
	 * 2.last_counter_value < cur_cyc < last_counter_value + arch_timer_threshold_in_cyc:
	 *   expected result, gap is smaller than arch_timer_threshold_in_cyc.
	 *
	 * 3.cur_cyc > last_counter_value + arch_timer_threshold_in_cyc:
	 *   maybe timer jump forward, gap is bigger than arch_timer_threshold_in_cyc
	 */
	if (likely(gap < arch_timer_threshold_in_cyc))
		goto out;

	/*
	 * If the cpu not invoke this interface for a long time,
	 * gap is bigger than the threshold and no error actually
	 * occurred.In this case, cur_cyc is very close to
	 * syscnt_value.
	 */
	syscnt_value = read_syscnt();
	if ((syscnt_value - cur_cyc) < APPROXIMATE_DIFF_IN_CYCLE)
		goto out;

	timer_jump_save_info(jump_dfx, cur_cyc, syscnt_value);
	cur_cyc = syscnt_value;
	gap = syscnt_value - jump_dfx->last_counter_value;
	if (unlikely(gap >= arch_timer_threshold_in_cyc))
		goto update_cur_cyc;

out:
	if (gap > jump_dfx->arch_timer_max_interval)
		jump_dfx->arch_timer_max_interval = gap;

update_cur_cyc:
	jump_dfx->last_counter_value = cur_cyc;

	local_irq_restore(flags);
	return cur_cyc;
}

static enum hrtimer_restart hrtimer_handler(struct hrtimer *timer)
{
	struct arch_timer_jump_dfx *jump_dfx;
	struct arch_timer_jump_info *jump_info;
	unsigned int cpu;
	u64 gap;
	static unsigned int times;

	if (times >= NR_CPUS)
		return HRTIMER_NORESTART;

	for_each_online_cpu(cpu) {
		if (!housekeeping_cpu(cpu, HK_FLAG_TICK))
			continue;

		jump_dfx = per_cpu_ptr(&arch_timer_jump_dfx, cpu);
		jump_info = &jump_dfx->arch_timer_jump_info;
		if (atomic_read(&jump_info->is_error_recorded) == 0)
			continue;

		gap = jump_info->syscnt_value - jump_info->old_cyc;
		if (gap < arch_timer_threshold_in_cyc)
			pr_err("[arch timer jump dfx] old_cyc:%llu, err_cyc:%llu, syscnt_value:%llu, fix ok\n",
					jump_info->old_cyc, jump_info->err_cyc, jump_info->syscnt_value);
		else
			pr_err("[arch timer jump dfx] old_cyc:%llu, err_cyc:%llu, syscnt_value:%llu, fix fail\n",
					jump_info->old_cyc, jump_info->err_cyc, jump_info->syscnt_value);
		smp_mb();
		atomic_set(&jump_info->is_error_recorded, 0);
		times++;
	}

	hrtimer_forward(timer, timer->base->get_time(), ktime_set(ERR_CHECK_PERIOD_IN_SECOND, 0));
	return HRTIMER_RESTART;
}

static acpi_status arch_timer_jump_dfx_acpi_init(acpi_handle handle, u32 level,
	void *context, void **retval)
{
	int ret;
	struct acpi_device device;
	u64 timer_max_gap_in_second;
	u64 syscnt_paddr;

	acpi_get_acpi_device(handle, &device, RTOS_ACPI_HID_TIME_JUMP_DFX);
	if (device.handle != handle)
		return AE_ERROR;

	ret = acpi_getprop_u64(&syscnt_paddr, &device, "syscnt_phys_addr");
	if (ret < 0)
		return AE_ERROR;
	/* syscnt_paddr max length is 48 bits */
	if (syscnt_paddr == 0 || (syscnt_paddr & PHYADDR_48BITS_MASK) != 0) {
		pr_err("syscnt_phys_addr is invalid\n");
		return AE_ERROR;
	}

	ret = acpi_getprop_u64(&timer_max_gap_in_second, &device, "timer_max_gap");
	if (ret < 0)
		return AE_ERROR;
	if (timer_max_gap_in_second > TIMER_GAP_MAX_SECONDS || timer_max_gap_in_second < TIMER_GAP_MIN_SECONDS) {
		pr_err("timer_max_gap:%llu is not in [%d, %d]\n",
			timer_max_gap_in_second, TIMER_GAP_MIN_SECONDS, TIMER_GAP_MAX_SECONDS);
		return AE_ERROR;
	}

	syscnt_vaddr = ioremap(syscnt_paddr, CNTCV_SIZE);
	if (syscnt_vaddr == NULL) {
		pr_err("syscnt register ioremap failed\n");
		return AE_ERROR;
	}

	arch_timer_threshold_in_cyc = ns_to_cyc(timer_max_gap_in_second * NSEC_PER_SEC);
	return AE_OK;
}

static void init_last_counter_value(void)
{
	unsigned int cpu;
	struct arch_timer_jump_dfx *jump_dfx;

	for_each_present_cpu(cpu) {
		jump_dfx = per_cpu_ptr(&arch_timer_jump_dfx, cpu);
		jump_dfx->last_counter_value = __arch_counter_get_cntpct();
	}
}

/*
 * if no acpi configuration is found or the configuration is incorrect,
 * arch_timer_threshold_in_cyc is not set and the initial value is 0.
 */
static inline bool is_threshold_set(void)
{
	return (arch_timer_threshold_in_cyc != 0);
}

static int __init arch_timer_jump_dfx_init(void)
{
	struct ctl_table_header *sysctl_header;

	if (acpi_disabled)
		return -ENODEV;

	(void)acpi_get_devices(RTOS_ACPI_HID_TIME_JUMP_DFX, arch_timer_jump_dfx_acpi_init, NULL, NULL);
	/*
	 * if no device is found in acpi_get_devices(), AE_OK is still returned.
	 * therefore, use other methods to check whether the acpi configuration
	 * takes effect.
	 */
	if (!is_threshold_set())
		return -ENODEV;

	sysctl_header = register_sysctl("kernel", arch_timer_jump_dfx_table);
	if (sysctl_header != NULL) {
		kmemleak_not_leak(sysctl_header);
	} else {
		iounmap(syscnt_vaddr);
		return -ENOMEM;
	}

	init_last_counter_value();

	hrtimer_init(&hrtimer_timer_exception, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	hrtimer_timer_exception.function = hrtimer_handler;
	hrtimer_start(&hrtimer_timer_exception, ktime_set(ERR_CHECK_PERIOD_IN_SECOND, 0), HRTIMER_MODE_REL);

	/*
	 * if the interface that provides only the query function, fails to be created,
	 * the main function is not affected.
	 */
	if (!proc_create("arch_timer_max_interval", 0400, NULL, &arch_timer_counter_fops))
		pr_warn("arch_timer_max_interval create failed\n");

	static_branch_enable(&arch_timer_jump_dfx_enable);
	pr_info("arch timer jump dfx enabled\n");
	return 0;
}
module_init(arch_timer_jump_dfx_init);
